---
title: Business rules
summary: Rules enforced by PdfAPI.Client before, during, or after an API call.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-17
---

# Business rules

`PdfAPI.Client` enforces five rules of its own (base-URI normalization, extension trimming, default output format, silent-empty-result handling, and disabled tracking polling) and relies on five further rules enforced by the shared `PdfAPI.Core` collaborator its local members delegate to.

---

## BR-CLI-001 Base URI normalization

| Field | Value |
| --- | --- |
| Statement | The API base address is normalized by removing trailing slashes and is always suffixed with `v1/`; all client calls therefore target API version 1 and the version segment is never doubled |
| Trigger | `PdfClient` construction |
| Enforcement | `PdfAPI.Client.PdfClient..ctor` |
| Origin | client |
| Outcome | Base URI `<url>/v1/`; a caller-supplied URL with or without a trailing slash yields the same base |
| Related member | Applies to construction, prior to all remote members |
| Source | `PdfAPI.Client\PdfClient.cs:17` |

---

## BR-CLI-002 File extension trimming

| Field | Value |
| --- | --- |
| Statement | A file extension supplied to the typed convert overload is stripped of leading and trailing dots before being placed in the URL path |
| Trigger | `Convert(byte[], string, PdfFormat)` call |
| Enforcement | `PdfClient.Convert(byte[], string, PdfFormat)` |
| Origin | client |
| Outcome | Both `".docx"` and `"docx"` produce `v1/convert/docx` |
| Related member | [`Convert(byte[] file, string fileExtension, PdfFormat format = PdfFormat.Pdf)`](reference.md#convert-from-file-bytes) |
| Source | `PdfAPI.Client\PdfClient.cs:53` |

---

## BR-CLI-003 Default output format

| Field | Value |
| --- | --- |
| Statement | When no output format is specified, conversion requests default to `PdfFormat.Pdf` |
| Trigger | `Convert(byte[], string)` call omitting `format` |
| Enforcement | `IPdfClient.Convert` / `PdfClient.Convert` optional parameter default |
| Origin | client |
| Outcome | `?format=Pdf` on the request |
| Related member | [`Convert(byte[] file, string fileExtension, PdfFormat format = PdfFormat.Pdf)`](reference.md#convert-from-file-bytes) |
| Source | `PdfAPI.Client\IPdfClient.cs:10`; `PdfAPI.Client\PdfClient.cs:51` |

---

## BR-CLI-004 Empty result handling

| Field | Value |
| --- | --- |
| Statement | A POST response carrying no results yields the type default rather than an error |
| Trigger | Any `PostJson<T>` call returning an empty `Results` collection |
| Enforcement | `PdfClient.IsPdfa`, `Convert` (both overloads), `Merge` — `.Results.FirstOrDefault()` |
| Origin | client, over the `SAIF.Web.API.Clients.JsonHttpClient` result envelope |
| Outcome | `false` / `null` returned silently — a failed call is indistinguishable from a negative answer; see [Tech debt](tech-debt.md) |
| Related member | [`Convert(ConvertRequest request)`](reference.md#convert-from-request), [`Convert(byte[] file, string fileExtension, PdfFormat format = PdfFormat.Pdf)`](reference.md#convert-from-file-bytes), [`Merge(MergeRequest request)`](reference.md#merge-documents), [`IsPdfa(byte[] pdf)`](reference.md#validate-pdf-a-conformance) |
| Source | `PdfAPI.Client\PdfClient.cs:41,48,55,62` |

---

## BR-CLI-005 Tracking status polling disabled

| Field | Value |
| --- | --- |
| Statement | Tracking status lookups never poll: the API's retry interval and retry count are left at their defaults |
| Trigger | `Track(long)` call |
| Enforcement | `PdfClient.Track` omits both optional query parameters |
| Origin | client; defaults defined by `PdfAPI` `InfoController.CheckPdfTrackingStatus` |
| Outcome | Single, immediate status read (`retryIntervalMilliseconds=1000`, `retryCount=0`) |
| Related member | [`Track(long trackingId)`](reference.md#track-request-status) |
| Source | `PdfAPI.Client\PdfClient.cs:67` |

---

## BR-CLI-006 Valid PDF byte array check

| Field | Value |
| --- | --- |
| Statement | A byte array is a valid PDF only when it is non-null, at least 5 bytes long, begins with the `%PDF` magic bytes, and carries a `%PDF-` header prefix |
| Trigger | Any local member call |
| Enforcement | `PdfAPI.Core.PdfClientService.IsPdf` |
| Origin | `PdfAPI.Core` (shared contracts/services assembly shipped inside this package) |
| Outcome | `true`/`false`; never throws |
| Related member | [`IsPdf(byte[] pdf)`](reference.md#check-is-pdf) |
| Source | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-CLI-007 Invalid PDF exception

| Field | Value |
| --- | --- |
| Statement | Local PDF operations reject invalid input by throwing `PdfException` with the message `ValidatePdf: The file is not a valid pdf document.` |
| Trigger | `Version`, `Pages`, `HasFormFields`, `Flatten`, `Extract` |
| Enforcement | `PdfAPI.Core.PdfClientService.ValidatePdf`, reached through `PdfClient.cs:22-32` |
| Origin | `PdfAPI.Core` |
| Outcome | `PdfAPI.Core.PdfException` propagates to the caller |
| Related member | [`Version(byte[] pdf)`](reference.md#read-pdf-version), [`Pages(byte[] pdf)`](reference.md#count-pages), [`HasFormFields(byte[] pdf)`](reference.md#check-has-form-fields), [`Flatten(byte[] pdf)`](reference.md#flatten-form-fields), [`Extract(DocumentRange range)`](reference.md#extract-page-range) |
| Source | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-CLI-008 Flatten with no AcroForm

| Field | Value |
| --- | --- |
| Statement | Flattening a PDF with no AcroForm returns the input bytes unchanged |
| Trigger | `Flatten(byte[])` |
| Enforcement | `PdfAPI.Core.PdfClientService.Flatten` |
| Origin | `PdfAPI.Core` |
| Outcome | Original byte array returned; no re-encoding |
| Related member | [`Flatten(byte[] pdf)`](reference.md#flatten-form-fields) |
| Source | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-CLI-009 Page range extraction clamping

| Field | Value |
| --- | --- |
| Statement | Page-range extraction clamps ranges past the end of the document, skips ranges wholly past the end, treats a stop page of `-1` as "to the last page", honours `IsOriginal` by copying the whole document, and supports descending ranges |
| Trigger | `Extract(DocumentRange)` |
| Enforcement | `PdfAPI.Core.PdfClientService.ExtractPageRanges` |
| Origin | `PdfAPI.Core` |
| Outcome | `PdfResult` with `PdfProcessorType.iTextSharp` and `Message` set to the range description |
| Related member | [`Extract(DocumentRange range)`](reference.md#extract-page-range) |
| Source | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## BR-CLI-010 PDF version reporting

| Field | Value |
| --- | --- |
| Statement | The PDF version is reported as the `x.y` value from the `%PDF-` header, or an empty string when the header carries no parsable version |
| Trigger | `Version(byte[])` |
| Enforcement | `PdfAPI.Core.PdfClientService.Version` |
| Origin | `PdfAPI.Core` |
| Outcome | `"1.4"`-style string, or `""` |
| Related member | [`Version(byte[] pdf)`](reference.md#read-pdf-version) |
| Source | `PdfAPI.Core\Services\PdfClientService.cs` |

---

## Related documentation
- [Client index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Package](package.md)
- [Client reference](reference.md)
- [Endpoint map](endpoint-map.md)
- [Consumption](consumption.md)
- [Contracts](contracts.md)
- [Dependencies](dependencies.md)
- [Tech debt](tech-debt.md)

## Sources
- `PdfAPI.Client\PdfClient.cs`
- `PdfAPI.Client\IPdfClient.cs`
- `PdfAPI.Core\Services\PdfClientService.cs`
- `PdfAPI\Controllers\v1\InfoController.cs`
